package org.pwwong.scalautils.property

import scala.collection.mutable.{ Start, End,Index,NA,
                                  Location,
                                  ArrayBuffer,
                                  ObservableBuffer,
                                  Buffer,
                                  Message,
                                  Include,
                                  Remove,
                                  Subscriber,
                                  Update,
                                  Reset}

import scala.collection.jcl.WeakHashMap

import org.pwwong.scalautils.event.{Event,ChangeBroadcasting}
import org.pwwong.scalautils.property.listevents._
import org.pwwong.scalautils.signals.Signal

object ListProp {
  trait MyObservableBuffer[A,This <: MyObservableBuffer[A, This]] extends ObservableBuffer[A, This] { 
    self: This =>
    abstract override def update(n: Int, newelement: A){
      super.remove(n)
      super.insertAll(n,List(newelement))
    }
  }
  
  class MutableListImpl[T] extends ArrayBuffer[T] with MyObservableBuffer[T,MutableListImpl[T]]
  class BufferSubscriber[T](private val notifyFunc:(MutableListImpl[T],Message[(Location, T)])=>Unit) extends Subscriber[Message[(Location, T)],MutableListImpl[T]]{
    override def notify(a:MutableListImpl[T],b:Message[(Location, T)])= notifyFunc(a,b)
  }  
  def apply[T] = new ListProp[T]()
}

class ListProp[T] extends ChangeBroadcasting[ListPropEvent[T]] {
  import ListProp._
  
  protected val subSignalSet = new WeakHashMap[Event[_],T=>Event[_]]
  
  private[this] val list = new MutableListImpl[T]
  private[this] val bufListener = new BufferSubscriber[T](
    (a:MutableListImpl[T],b:Message[(Location, T)]) =>
	    b match {
            case include:Include[(Location, T)] => {
              for(entry <- subSignalSet) entry._2(include.elem._2) bind entry._1
              onAdd.fire(AddEvent(this, getIndex(include.elem._1), include.elem._1, include.elem._2))
            }
		    case update:Update[(Location, T)] => onUpdate.fire(UpdateEvent(this, getIndex(update.elem._1), update.elem._1, update.elem._2))
            case remove:Remove[(Location, T)] => {
              for(entry <- subSignalSet) entry._2(remove.elem._2) unbind entry._1
              onRemove.fire(RemoveEvent(this, getIndex(remove.elem._1), remove.elem._1, remove.elem._2))
            }
		    case clear:Reset[_] => onClear.fire(ClearEvent(this))
	    }
  )
  private def fireChange(a:ListPropEvent[T]) = onChange.fire(a)
  list.subscribe(bufListener);
  
  val onAdd = Event[AddEvent[T]](this)(fireChange)
  val onRemove = Event[RemoveEvent[T]](this)(fireChange)
  val onUpdate = Event[UpdateEvent[T]](this)(fireChange)
  val onClear = Event[ClearEvent[T]](this)(fireChange)
  
  def getIndex(loc:Location):Int = loc match {
    case Start => 0
    case End => list.size-1
    case ind:Index => ind.n
    case NA => 0
  }
  
  def apply():ObservableBuffer[T,MutableListImpl[T]] = list
  def apply(index:Int):T = list(index)
  def apply(newEls:T*):ListProp[T] = {
    list.clear()
    for(newElem <- newEls) list+newElem
    this
  }
  
  def bindOnSubEvent[SignalType](boundedSignal:Event[SignalType], subSignalTargeter:T=>Event[SignalType]):Event[SignalType] = {
    if(!subSignalSet.contains(boundedSignal)){
      subSignalSet+=(boundedSignal->subSignalTargeter)
      for(el <- list) subSignalTargeter(el) bind boundedSignal
    }
    boundedSignal
  }
  
  def unbindFromSubEvent[SignalType](boundedSignal:Event[SignalType]){
    if(subSignalSet.contains(boundedSignal)){
      val subSignalTargeter = subSignalSet(boundedSignal).asInstanceOf[T=>Event[SignalType]]
      for(el <- list) subSignalTargeter(el) unbind boundedSignal
    }
  }
}
